package com.hyhy.hycloud.uaa.security.provider;

import com.hyhy.hycloud.uaa.AppIdProperties;
import com.hyhy.hycloud.uaa.SecurityProperties;
import com.hyhy.hycloud.uaa.aop.OperateLog;
import com.hyhy.hycloud.uaa.security.LoginRequestDetails;
import com.hyhy.hycloud.uaa.security.PasswordType;
import com.hyhy.hycloud.uaa.security.SecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;

/**
 * 抽象安全认证提供者
 *
 * @author Xuegui Yuan
 * @date 2018-09-19
 */
@Slf4j
public abstract class AbstractAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    SecurityProperties securityProperties;

    @Autowired
    AppIdProperties appIdProperties;

    @Override
    @OperateLog(model = "登录页", subModel = "用户登录")
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Object authRequestDetails = authentication.getDetails();
        Optional principal = Optional.ofNullable(authentication.getPrincipal());
        Optional credentials = Optional.ofNullable(authentication.getCredentials());

        if (!principal.isPresent() || !credentials.isPresent()) {
            throw new BadCredentialsException("Invalid user credentials");
        }

        String principalStr = (String) principal.get();
        String appId = null;
        PasswordType passwordType = PasswordType.UNDEFINED;
        String tfaCode = null;

        if (authRequestDetails instanceof LoginRequestDetails) {
            LoginRequestDetails loginRequestDetails = (LoginRequestDetails) authRequestDetails;
            appId = loginRequestDetails.getAppId();
            passwordType = loginRequestDetails.getPasswordType();
            tfaCode = loginRequestDetails.getTfaCode();
        } else if (authRequestDetails instanceof Map) {
            Map authRequestMap = (Map) authRequestDetails;
            appId = (String) authRequestMap.get(LoginRequestDetails.APP_ID);
            String pt = (String) authRequestMap.get(LoginRequestDetails.PASSWORD_TYPE_KEY);
            tfaCode = (String) authRequestMap.get(LoginRequestDetails.TFA_CODE);
            if (StringUtils.isNoneBlank(pt)) {
                passwordType = PasswordType.valueOf(pt);
            }

        }

        if (!isSupportAuthRequest(passwordType, appId, principalStr)) {
            return null;
        }

        String pwd, tfa = null;
        try {
            pwd = new String(Base64.getDecoder().decode((String) credentials.get()), "UTF-8");
            if (tfaCode != null) {
                tfa = new String(Base64.getDecoder().decode(tfaCode), "UTF-8");
            }
        } catch (UnsupportedEncodingException e) {
            log.error("BASE64解密失败:", e.getMessage());
            throw new BadCredentialsException("Invalid BASE64 code");
//            return null;
        } catch (IllegalArgumentException iae) {
            log.error("BASE64解密失败:{}", iae.getMessage());
            throw new BadCredentialsException("Invalid BASE64 code");
//            return null;
        }

        UserDetails userDetails = authenticationCredentials(appId, principalStr, pwd);

        // 双重验证
        if (securityProperties.isEnableTfa() && appIdProperties.getTfaApps().contains(appId)) {
            authenticateTfaCode(appId, (SecurityUser) userDetails, tfa);
        }

        Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
        return new UsernamePasswordAuthenticationToken(userDetails, pwd, authorities);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

    /**
     * 是否支持认证请求
     *
     * @param passwordType 密码类型
     * @param appId        APP ID
     * @param principal    账号
     * @return 支持-true 不支持-false
     */
    abstract boolean isSupportAuthRequest(PasswordType passwordType, String appId, String principal);

    /**
     * 认证凭证
     *
     * @param appId       应用ID
     * @param principal   账户：用户名、手机号、邮箱等
     * @param credentials 凭证：密码、验证码
     * @return 用户详细信息
     */
    abstract UserDetails authenticationCredentials(String appId, String principal, String credentials);

    /**
     * 双重验证
     *
     * @param appId        应用ID
     * @param securityUser 用户信息
     * @param tfaCode      双重验证码
     */
    public void authenticateTfaCode(String appId, SecurityUser securityUser, String tfaCode) {
    }
}
